/* GnomeTyper typer application
 * Copyright (C) 2013 Juan Manuel Borges Caño <juanmabcmail@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gnome-typer.h"
#include <gdk/gdkkeysyms.h>

#include <string.h>

G_DEFINE_TYPE (GnomeTyper, gnome_typer, GTK_TYPE_APPLICATION);

void
gnome_typer_count (GnomeTyper * typer, gchar * str, guint * lines,
    guint * characters)
{
  gchar **split;

  *characters = 0;
  split = g_strsplit (str, "\n", -1);
  for (*lines = 0; split[*lines]; (*lines)++)
    *characters += g_utf8_strlen (g_strstrip (split[*lines]), -1);
  g_strfreev (split);
  (*lines)--;
}

gboolean
gnome_typer_bar_status_timer_timeout (gpointer data)
{
  GnomeTyper *typer = GNOME_TYPER (data);
  gboolean ret;

  if ((ret = !g_source_is_destroyed (g_main_current_source ()))) {
    gulong ms;
    gdouble seconds;
    gchar *tmp;

    gtk_statusbar_pop (GTK_STATUSBAR (typer->bar.status), 0);
    seconds = g_timer_elapsed (typer->timing.timer, &ms);
    tmp =
        g_strdup_printf
        ("Typing: %i / %i line characters, %i / %i characters, %i / %i lines, %.3f seconds",
        typer->progress.character.tmp,
        gtk_entry_get_text_length (GTK_ENTRY (typer->info.present)),
        typer->progress.character.status + typer->progress.character.tmp,
        typer->progress.character.count, typer->progress.line.status,
        typer->progress.line.count, seconds);
    gtk_statusbar_push (GTK_STATUSBAR (typer->bar.status), 0, tmp);
    g_free (tmp);
  }

  return ret;
}

static void
gnome_typer_prepare_task (GnomeTyper * typer, const gchar * title)
{
  gchar *line = NULL;

  gtk_statusbar_pop (GTK_STATUSBAR (typer->bar.status), 0);

  gtk_window_set_title (GTK_WINDOW (typer->window), title);

  typer->progress.character.status = typer->progress.character.tmp =
      typer->progress.line.status = 0;

  gtk_entry_set_text (GTK_ENTRY (typer->info.past), "");

  if (typer->stream.data.in && (line =
          g_data_input_stream_read_line (typer->stream.data.in, NULL, NULL,
              NULL))) {
    gchar *tmp;

    gtk_entry_set_text (GTK_ENTRY (typer->info.present), g_strstrip (line));
    g_free (line);
    line = NULL;

    gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (typer->bar.progress), 0);

    if (typer->stream.data.in && (line =
            g_data_input_stream_read_line (typer->stream.data.in, NULL, NULL,
                NULL))) {
      gtk_entry_set_text (GTK_ENTRY (typer->info.future), g_strstrip (line));
      g_free (line);
      line = NULL;
    } else
      gtk_entry_set_text (GTK_ENTRY (typer->info.future), "");

    gtk_entry_set_text (GTK_ENTRY (typer->info.entry), "");
    gtk_editable_set_editable (GTK_EDITABLE (typer->info.entry), TRUE);

    tmp =
        g_strdup_printf ("Type!: %i line characters, %i characters, %i lines",
        gtk_entry_get_text_length (GTK_ENTRY (typer->info.present)),
        typer->progress.character.count, typer->progress.line.count);
    gtk_statusbar_push (GTK_STATUSBAR (typer->bar.status), 0, tmp);
    g_free (tmp);
  } else {
    gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (typer->bar.progress), 100);

    gtk_entry_set_text (GTK_ENTRY (typer->info.present), "");
    gtk_entry_set_text (GTK_ENTRY (typer->info.future), "");

    gtk_statusbar_push (GTK_STATUSBAR (typer->bar.status), 0, "No data");
  }
}

static void
gnome_typer_clear_task (GnomeTyper * typer)
{
  if (typer->timing.sourceid) {
    g_source_remove (typer->timing.sourceid);
    typer->timing.sourceid = 0;
  }

  if (typer->stream.file) {
    if (typer->stream.fileio)
      g_io_stream_close (G_IO_STREAM (typer->stream.fileio), NULL, NULL);
    else {
      if (typer->stream.in)
        g_input_stream_close (typer->stream.in, NULL, NULL);
      if (typer->stream.out)
        g_output_stream_close (typer->stream.out, NULL, NULL);

    }
    if (typer->stream.data.in) {
      g_object_unref (typer->stream.data.in);
      typer->stream.data.in = NULL;
    }

    g_object_unref (typer->stream.file);
    typer->stream.file = NULL;
  }
}

static void
gnome_typer_start_task (GnomeTyper * typer)
{
  g_timer_start (typer->timing.timer);
  typer->timing.sourceid =
      gdk_threads_add_timeout (10, gnome_typer_bar_status_timer_timeout, typer);
}

static void
gnome_typer_end_task (GnomeTyper * typer)
{
  gdouble seconds;
  gchar *tmp;
  gulong ms;

  gtk_statusbar_pop (GTK_STATUSBAR (typer->bar.status), 0);

  gnome_typer_clear_task (typer);

  gtk_editable_set_editable (GTK_EDITABLE (typer->info.entry), FALSE);

  g_timer_stop (typer->timing.timer);
  seconds = g_timer_elapsed (typer->timing.timer, &ms);

  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (typer->bar.progress),
      (float) typer->progress.character.status /
      (float) typer->progress.character.count);

  tmp =
      g_strdup_printf
      ("Completed: %i characters, %i lines, %.3f seconds, %.3f characters / second",
      typer->progress.character.status, typer->progress.line.status, seconds,
      (double) typer->progress.character.status / seconds);
  gtk_statusbar_push (GTK_STATUSBAR (typer->bar.status), 0, tmp);
  g_free (tmp);
}

static void
gnome_typer_lesson (GnomeTyper * typer, guint characters, guint groups,
    guint lines)
{
  gnome_typer_clear_task (typer);

  if ((typer->stream.file = g_file_new_tmp (NULL, &typer->stream.fileio, NULL))) {
    guint character, group, line;
    gchar output[(80 + 1) * lines + 1];
    gchar input[characters];
    gsize written;
    GRand *rand;

    typer->stream.in =
        g_io_stream_get_input_stream (G_IO_STREAM (typer->stream.fileio));
    typer->stream.out =
        g_io_stream_get_output_stream (G_IO_STREAM (typer->stream.fileio));
    typer->stream.data.in = g_data_input_stream_new (typer->stream.in);

    rand = g_rand_new ();
    for (character = 0; character < characters; character++) {
      input[character] = 'a' + g_rand_int_range (rand, 0, 'z' - 'a');
    }
    memset (output, ' ', (80 + 1) * lines + 1);
    for (line = 0; line < lines; line++) {
      for (group = 0; group < 80 / (groups + 1); group++) {
        for (character = 0; character < groups; character++) {
          output[line * (80 + 1) + group * (groups + 1) + character] =
              input[g_rand_int_range (rand, 0, characters)];
        }
        output[line * (80 + 1) + group * (groups + 1) + character] = ' ';
      }
      output[line * (80 + 1) + group * (groups + 1)] = '\n';
    }
    output[line * (80 + 1)] = '\0';
    g_rand_free (rand);
    g_output_stream_write_all (typer->stream.out, output,
        strlen (output) * sizeof (gchar), &written, NULL, NULL);
    g_seekable_seek (G_SEEKABLE (typer->stream.fileio), 0, G_SEEK_SET, NULL,
        NULL);

    gnome_typer_count (typer, output, &typer->progress.line.count,
        &typer->progress.character.count);

    gnome_typer_prepare_task (typer, PACKAGE_NAME " Lesson");
  }
}

static void
gnome_typer_lesson1 (GnomeTyper * typer)
{
  gnome_typer_lesson (typer, 2, 4, 4);
}

static void
gnome_typer_lesson2 (GnomeTyper * typer)
{
  gnome_typer_lesson (typer, 4, 4, 5);
}

static void
gnome_typer_lesson3 (GnomeTyper * typer)
{
  gnome_typer_lesson (typer, 6, 4, 6);
}

static void
gnome_typer_lesson4 (GnomeTyper * typer)
{
  gnome_typer_lesson (typer, 8, 6, 7);
}

static void
gnome_typer_lesson5 (GnomeTyper * typer)
{
  gnome_typer_lesson (typer, 10, 6, 8);
}

static void
gnome_typer_lesson6 (GnomeTyper * typer)
{
  gnome_typer_lesson (typer, 12, 6, 10);
}

static void
gnome_typer_lesson7 (GnomeTyper * typer)
{
  gnome_typer_lesson (typer, 14, 8, 12);
}

static void
gnome_typer_lesson8 (GnomeTyper * typer)
{
  gnome_typer_lesson (typer, 16, 8, 14);
}

static void
gnome_typer_lesson9 (GnomeTyper * typer)
{
  gnome_typer_lesson (typer, 18, 8, 16);
}

static void
gnome_typer_lesson10 (GnomeTyper * typer)
{
  gnome_typer_lesson (typer, 20, 10, 18);
}

static void
gnome_typer_lesson11 (GnomeTyper * typer)
{
  gnome_typer_lesson (typer, 23, 10, 20);
}

static void
gnome_typer_lesson12 (GnomeTyper * typer)
{
  gnome_typer_lesson (typer, 26, 10, 30);
}

static void
gnome_typer_open (GnomeTyper * typer)
{
  GtkWidget *chooser;

  chooser =
      gtk_file_chooser_dialog_new ("Open file ...", GTK_WINDOW (typer->window),
      GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
      GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
  gtk_widget_show_all (chooser);
  if (gtk_dialog_run (GTK_DIALOG (chooser)) == GTK_RESPONSE_ACCEPT) {
    gchar *uri;

    uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (chooser));

    gnome_typer_clear_task (typer);

    if ((typer->stream.file = g_file_new_for_uri (uri))) {
      gchar *tmp, *output;

      typer->stream.fileio = NULL;
      typer->stream.in =
          G_INPUT_STREAM (g_file_read (typer->stream.file, NULL, NULL));
      typer->stream.out = NULL;
      typer->stream.data.in = g_data_input_stream_new (typer->stream.in);

      tmp =
          g_strdup_printf
          ("sh -c \"sed -e 's/^[[:blank:]]\\+//g' -e 's/[[:blank:]]\\+$//g' \\\"%s\\\" | wc -c -l\"",
          g_file_get_path (typer->stream.file));
      g_spawn_command_line_sync (tmp, &output, NULL, NULL, NULL);
      g_free (tmp);
      sscanf (output, "  %i %i", &typer->progress.line.count,
          &typer->progress.character.count);
      typer->progress.character.count -= typer->progress.line.count;

      tmp =
          g_strdup_printf ("" PACKAGE_NAME " File: %s",
          g_file_get_basename (typer->stream.file));
      gnome_typer_prepare_task (typer, tmp);
      g_free (tmp);
    }
    g_free (uri);
  }
  gtk_widget_destroy (chooser);
}

static void
gnome_typer_fortune (GnomeTyper * typer)
{
  gnome_typer_clear_task (typer);

  if ((typer->stream.file = g_file_new_tmp (NULL, &typer->stream.fileio, NULL))) {
    gchar *output;
    gsize written;

    typer->stream.in =
        g_io_stream_get_input_stream (G_IO_STREAM (typer->stream.fileio));
    typer->stream.out =
        g_io_stream_get_output_stream (G_IO_STREAM (typer->stream.fileio));
    typer->stream.data.in = g_data_input_stream_new (typer->stream.in);

    g_spawn_command_line_sync
        ("sh -c \"fortune | sed -e 's/^[[:blank:]]\\+//g' -e 's/[[:blank:]]\\+$//g'\"",
        &output, NULL, NULL, NULL);
    g_output_stream_write_all (typer->stream.out, output,
        strlen (output) * sizeof (gchar), &written, NULL, NULL);
    g_seekable_seek (G_SEEKABLE (typer->stream.fileio), 0, G_SEEK_SET, NULL,
        NULL);

    gnome_typer_count (typer, output, &typer->progress.line.count,
        &typer->progress.character.count);
    g_free (output);

    gnome_typer_prepare_task (typer, PACKAGE_NAME " Fortune");
  }
}

static void
gnome_typer_help (GnomeTyper * typer)
{
  gtk_show_uri (NULL, "http://en.wikipedia.org/wiki/Typing", GDK_CURRENT_TIME,
      NULL);
}

static void
gnome_typer_about (GnomeTyper * typer)
{
  const gchar *authors[] = {
    "Juan Manuel Borges Caño <juanmabcmail@gmail.com>",
    NULL
  };
  const gchar *artists[] = {
    "Juan Manuel Borges Caño <juanmabcmail@gmail.com>",
    "Akira Tagoh <tagoh@redhat.com>",
    NULL
  };
  const gchar *documenters[] = {
    "Wikipedia Typing Article http://en.wikipedia.org/wiki/Typing",
    NULL
  };
  const gchar *copyright = "Copyright © Juan Manuel Borges Caño";
  const gchar *license =
      "Copyright (C) 2013 Juan Manuel Borges Caño <juanmabcmail@gmail.com>\n"
      "\n"
      "This program is free software; you can redistribute it and/or "
      "modify it under the terms of the GNU Library General Public "
      "License as published by the Free Software Foundation; either "
      "version 2 of the License, or (at your option) any later version.\n"
      "\n"
      "This program is distributed in the hope that it will be useful, "
      "but WITHOUT ANY WARRANTY; without even the implied warranty of "
      "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU "
      "Library General Public License for more details.\n"
      "\n"
      "You should have received a copy of the GNU Library General Public "
      "License along with this program; if not, write to the "
      "Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, "
      "Boston, MA 02110-1301, USA.\n";
  const gchar *comments = PACKAGE_DESCRIPTION;
  const gchar *version = VERSION;
  const gchar *website = PACKAGE_HOMEPAGE;
  gtk_show_about_dialog (GTK_WINDOW (typer->window), "authors", authors,
      "artists", artists, "documenters", documenters, "copyright", copyright,
      "license-type", GTK_LICENSE_GPL_3_0, "license", license, "comments",
      comments, "version", version, "website", website, NULL);
}

static gboolean
gnome_typer_info_entry_on_draw (GtkWidget * widget, GdkEventExpose * event,
    gpointer data)
{
  GnomeTyper *typer = GNOME_TYPER (data);
  const gchar *outtext = gtk_entry_get_text (GTK_ENTRY (typer->info.present));
  const gchar *intext = gtk_entry_get_text (GTK_ENTRY (widget));
  PangoLayout *layout = gtk_entry_get_layout (GTK_ENTRY (widget));
  PangoAttribute *tint;
  PangoAttribute *error;
  const glong inlen = g_utf8_strlen (intext, -1), outlen =
      g_utf8_strlen (outtext, -1);
  glong i;

  pango_attr_list_unref (typer->attribute);
  typer->attribute = pango_attr_list_new ();

  typer->progress.character.tmp = 0;
  for (i = 0; i < inlen; i++) {
    gchar *inoffset = g_utf8_offset_to_pointer (intext, i), *outoffset =
        g_utf8_offset_to_pointer (outtext, i);
    if (i > outlen || g_utf8_get_char (inoffset) != g_utf8_get_char (outoffset)) {
      tint = pango_attr_underline_color_new (65535, 0, 0);
      error = pango_attr_underline_new (PANGO_UNDERLINE_ERROR);
      tint->start_index = error->start_index = inoffset - intext;
      tint->end_index = error->end_index =
          g_utf8_find_next_char (inoffset, NULL) - intext;
      pango_attr_list_insert (typer->attribute, tint);
      pango_attr_list_insert (typer->attribute, error);
    } else
      typer->progress.character.tmp++;
  }
  pango_layout_set_attributes (layout, typer->attribute);

  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (typer->bar.progress),
      (float) (typer->progress.character.status +
          typer->progress.character.tmp) /
      (float) typer->progress.character.count);

  if (!typer->timing.sourceid
      && typer->progress.character.status + typer->progress.character.tmp +
      gtk_entry_get_text_length (GTK_ENTRY (typer->info.entry))
      && typer->progress.character.status < typer->progress.character.count)
    gnome_typer_start_task (typer);


  return FALSE;
}

static gboolean
gnome_typer_info_entry_on_key_press_event (GtkWidget * widget,
    GdkEventKey * event, gpointer data)
{
  if (event->keyval == GDK_KEY_Tab && !(event->state & GDK_CONTROL_MASK)) {
    gint position = gtk_editable_get_position (GTK_EDITABLE (widget));
    gtk_editable_insert_text (GTK_EDITABLE (widget), "\t", 1, &position);
    gtk_editable_set_position (GTK_EDITABLE (widget), position);
    return TRUE;
  } else
    return FALSE;
}

static gboolean
gnome_typer_on_event (GtkWidget * widget, GdkEvent * event, gpointer data)
{
  GnomeTyper *typer = GNOME_TYPER (data);

  if (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE)
    gtk_widget_event (typer->keyboard.layout, event);

  return FALSE;
}

static void
gnome_typer_info_entry_on_activate (GtkEntry * entry, gpointer data)
{
  GnomeTyper *typer = GNOME_TYPER (data);
  const gchar *outtext = gtk_entry_get_text (GTK_ENTRY (typer->info.present));
  const gchar *intext = gtk_entry_get_text (entry);

  if (typer->progress.line.status < typer->progress.line.count) {
    if (g_str_equal (outtext, intext)) {
      gtk_entry_set_text (GTK_ENTRY (typer->info.past),
          gtk_entry_get_text (GTK_ENTRY (typer->info.present)));
      gtk_entry_set_text (GTK_ENTRY (typer->info.present),
          gtk_entry_get_text (GTK_ENTRY (typer->info.future)));
      if (typer->stream.file) {
        gchar *line = NULL;

        if (typer->stream.data.in && (line =
                g_data_input_stream_read_line (typer->stream.data.in, NULL,
                    NULL, NULL))) {
          gtk_entry_set_text (GTK_ENTRY (typer->info.future),
              g_strstrip (line));
          g_free (line);
          line = NULL;
        } else {
          if (typer->stream.fileio)
            g_io_stream_close (G_IO_STREAM (typer->stream.fileio), NULL, NULL);
          else {
            if (typer->stream.data.in) {
              g_object_unref (typer->stream.data.in);
              typer->stream.data.in = NULL;
            }

            if (typer->stream.in)
              g_input_stream_close (typer->stream.in, NULL, NULL);
            if (typer->stream.out)
              g_output_stream_close (typer->stream.out, NULL, NULL);
          }

          gtk_entry_set_text (GTK_ENTRY (typer->info.future), "");
        }
      }

      gtk_entry_set_text (entry, "");

      typer->progress.character.status += typer->progress.character.tmp;
      typer->progress.line.status++;


      gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (typer->bar.progress),
          (float) typer->progress.character.status /
          (float) typer->progress.character.count);

      if (typer->progress.line.status == typer->progress.line.count)
        gnome_typer_end_task (typer);

    }
  }
}

#define gnome_typer_on_lesson(lesson) \
	static void \
	gnome_typer_on_lesson##lesson(GtkAction *action, gpointer data) \
	{ \
		gnome_typer_lesson##lesson(data); \
	}

gnome_typer_on_lesson (1);
gnome_typer_on_lesson (2);
gnome_typer_on_lesson (3);
gnome_typer_on_lesson (4);
gnome_typer_on_lesson (5);
gnome_typer_on_lesson (6);
gnome_typer_on_lesson (7);
gnome_typer_on_lesson (8);
gnome_typer_on_lesson (9);
gnome_typer_on_lesson (10);
gnome_typer_on_lesson (11);
gnome_typer_on_lesson (12);

static void
gnome_typer_on_file (GtkAction * action, gpointer data)
{
  gnome_typer_open (data);
}

static void
gnome_typer_on_fortune (GtkAction * action, gpointer data)
{
  gnome_typer_fortune (data);
}

static void
gnome_typer_on_help (GtkAction * action, gpointer data)
{
  gnome_typer_help (data);
}

static void
gnome_typer_on_about (GtkAction * action, gpointer data)
{
  gnome_typer_about (data);
}

static const gchar *gnome_typer_ui_info =
    "<ui>"
    "  <toolbar name='ToolBar'>"
    "    <toolitem name='win.lesson1' action='Lesson 1'/>"
    "    <toolitem name='win.lesson2' action='Lesson 2'/>"
    "    <toolitem name='win.lesson3' action='Lesson 3'/>"
    "    <toolitem name='win.lesson4' action='Lesson 4'/>"
    "    <toolitem name='win.lesson5' action='Lesson 5'/>"
    "    <toolitem name='win.lesson6' action='Lesson 6'/>"
    "    <toolitem name='win.lesson7' action='Lesson 7'/>"
    "    <toolitem name='win.lesson8' action='Lesson 8'/>"
    "    <toolitem name='win.lesson9' action='Lesson 9'/>"
    "    <toolitem name='win.lesson10' action='Lesson 10'/>"
    "    <toolitem name='win.lesson11' action='Lesson 11'/>"
    "    <toolitem name='win.lesson12' action='Lesson 12'/>"
    "    <separator/>"
    "    <toolitem name='win.open' action='Open'/>"
    "    <toolitem name='win.fortune' action='Fortune'/>"
    "    <separator/>"
    "    <toolitem name='win.help' action='Help'/>"
    "    <toolitem name='win.about' action='About'/>" "  </toolbar>" "</ui>";

static void
gnome_typer_toolbar_init (GnomeTyper * typer)
{
  const GtkActionEntry entries[] = {
    {"Lesson 1", NULL, "#_1", "<control>1", "Type a lesson",
        G_CALLBACK (gnome_typer_on_lesson1)},
    {"Lesson 2", NULL, "#_2", "<control>2", "Type a lesson",
        G_CALLBACK (gnome_typer_on_lesson2)},
    {"Lesson 3", NULL, "#_3", "<control>3", "Type a lesson",
        G_CALLBACK (gnome_typer_on_lesson3)},
    {"Lesson 4", NULL, "#_4", "<control>4", "Type a lesson",
        G_CALLBACK (gnome_typer_on_lesson4)},
    {"Lesson 5", NULL, "#_5", "<control>5", "Type a lesson",
        G_CALLBACK (gnome_typer_on_lesson5)},
    {"Lesson 6", NULL, "#_6", "<control>6", "Type a lesson",
        G_CALLBACK (gnome_typer_on_lesson6)},
    {"Lesson 7", NULL, "#_7", "<control>7", "Type a lesson",
        G_CALLBACK (gnome_typer_on_lesson7)},
    {"Lesson 8", NULL, "#_8", "<control>8", "Type a lesson",
        G_CALLBACK (gnome_typer_on_lesson8)},
    {"Lesson 9", NULL, "#_9", "<control>9", "Type a lesson",
        G_CALLBACK (gnome_typer_on_lesson9)},
    {"Lesson 10", NULL, "#1_0", "<control>0", "Type a lesson",
        G_CALLBACK (gnome_typer_on_lesson10)},
    {"Lesson 11", NULL, "#11", "<control>a", "Type a lesson",
        G_CALLBACK (gnome_typer_on_lesson11)},
    {"Lesson 12", NULL, "#12", "<control>b", "Type a lesson",
        G_CALLBACK (gnome_typer_on_lesson12)},
    {"Open", GTK_STOCK_OPEN, "_Open text file...", "<control>o",
        "Type a text file", G_CALLBACK (gnome_typer_on_file)},
    {"Fortune", GTK_STOCK_EXECUTE, "_Fortune epigram", "<control>f",
        "Type a fortune epigram", G_CALLBACK (gnome_typer_on_fortune)},
    {"Help", GTK_STOCK_HELP, "_Help", "<control>h", "Contents",
        G_CALLBACK (gnome_typer_on_help)},
    {"About", GTK_STOCK_ABOUT, "_About...", "<control>a", "About " PACKAGE_NAME,
        G_CALLBACK (gnome_typer_on_about)}
  };
  guint n_entries = G_N_ELEMENTS (entries);
  GtkActionGroup *actions;

  actions = gtk_action_group_new ("Actions");
  gtk_action_group_add_actions (actions, entries, n_entries, typer);
  typer->controller = gtk_ui_manager_new ();
  gtk_ui_manager_insert_action_group (typer->controller, actions, 0);
  gtk_window_add_accel_group (GTK_WINDOW (typer->window),
      gtk_ui_manager_get_accel_group (typer->controller));
  gtk_ui_manager_add_ui_from_string (typer->controller, gnome_typer_ui_info, -1,
      NULL);
}

static GtkWidget *
gnome_typer_info_init (GnomeTyper * typer)
{
  GtkWidget *vbox;
  GdkColor tint;

  typer->stream.file = NULL;

  typer->timing.sourceid = 0;
  typer->timing.timer = g_timer_new ();

  typer->progress.character.status = typer->progress.line.status = 0;
  typer->progress.character.count = typer->progress.line.count = 0;

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);

  typer->info.past = gtk_entry_new ();
  gtk_editable_set_editable (GTK_EDITABLE (typer->info.past), FALSE);
  gtk_entry_set_alignment (GTK_ENTRY (typer->info.past), 0.5f);
  gdk_color_parse ("green", &tint);
  gtk_widget_modify_bg (typer->info.past, GTK_STATE_NORMAL, &tint);
  gtk_widget_set_sensitive (typer->info.past, FALSE);
  gtk_widget_set_tooltip_text (typer->info.past,
      "Text that you typed correctly");
  gtk_box_pack_start (GTK_BOX (vbox), typer->info.past, FALSE, TRUE, 0);

  typer->info.present = gtk_entry_new ();
  gtk_editable_set_editable (GTK_EDITABLE (typer->info.present), FALSE);
  gtk_entry_set_alignment (GTK_ENTRY (typer->info.present), 0.5f);
  gdk_color_parse ("yellow", &tint);
  gtk_widget_modify_bg (typer->info.present, GTK_STATE_NORMAL, &tint);
  gtk_widget_set_tooltip_text (typer->info.present, "Text that you must type");
  gtk_box_pack_start (GTK_BOX (vbox), typer->info.present, FALSE, TRUE, 0);

  typer->info.future = gtk_entry_new ();
  gtk_editable_set_editable (GTK_EDITABLE (typer->info.future), FALSE);
  gtk_entry_set_alignment (GTK_ENTRY (typer->info.future), 0.5f);
  gdk_color_parse ("red", &tint);
  gtk_widget_modify_bg (typer->info.future, GTK_STATE_NORMAL, &tint);
  gtk_widget_set_sensitive (typer->info.future, FALSE);
  gtk_widget_set_tooltip_text (typer->info.future, "Text that you will type");
  gtk_box_pack_start (GTK_BOX (vbox), typer->info.future, FALSE, TRUE, 0);

  typer->info.entry = gtk_entry_new ();
  gtk_editable_set_editable (GTK_EDITABLE (typer->info.entry), FALSE);
  gtk_entry_set_alignment (GTK_ENTRY (typer->info.entry), 0.5f);
  gtk_widget_set_tooltip_text (typer->info.entry, "Where you must type");
  gtk_box_pack_start (GTK_BOX (vbox), typer->info.entry, FALSE, TRUE, 0);

  typer->attribute = pango_attr_list_new ();

  g_signal_connect (G_OBJECT (typer->window), "event",
      G_CALLBACK (gnome_typer_on_event), typer);
  g_signal_connect (G_OBJECT (typer->info.entry), "key-press-event",
      G_CALLBACK (gnome_typer_info_entry_on_key_press_event), typer);
  g_signal_connect (G_OBJECT (typer->info.entry), "draw",
      G_CALLBACK (gnome_typer_info_entry_on_draw), typer);
  g_signal_connect (G_OBJECT (typer->info.entry), "activate",
      G_CALLBACK (gnome_typer_info_entry_on_activate), typer);

  return vbox;
}

static void
gnome_typer_keyboard_init (GnomeTyper * typer)
{
  typer->keyboard.layout = gkbd_keyboard_drawing_new ();
  gkbd_keyboard_drawing_set_groups_levels (GKBD_KEYBOARD_DRAWING
      (typer->keyboard.layout), typer->keyboard.pgrouplevels);
  gkbd_keyboard_drawing_set_track_modifiers (GKBD_KEYBOARD_DRAWING
      (typer->keyboard.layout), TRUE);
  gkbd_keyboard_drawing_set_track_config (GKBD_KEYBOARD_DRAWING
      (typer->keyboard.layout), TRUE);
  memset (&typer->keyboard.names, '\0', sizeof (typer->keyboard.names));
  typer->keyboard.names.symbols = (gchar *)
      gkbd_keyboard_drawing_get_symbols (GKBD_KEYBOARD_DRAWING (typer->
          keyboard.layout));
  typer->keyboard.names.keycodes = (gchar *)
      gkbd_keyboard_drawing_get_keycodes (GKBD_KEYBOARD_DRAWING
      (typer->keyboard.layout));
  typer->keyboard.names.geometry = (gchar *)
      gkbd_keyboard_drawing_get_geometry (GKBD_KEYBOARD_DRAWING
      (typer->keyboard.layout));
  typer->keyboard.names.types =
      (gchar *)
      gkbd_keyboard_drawing_get_types (GKBD_KEYBOARD_DRAWING (typer->keyboard.
          layout));
  typer->keyboard.names.compat =
      (gchar *)
      gkbd_keyboard_drawing_get_compat (GKBD_KEYBOARD_DRAWING (typer->keyboard.
          layout));
  gkbd_keyboard_drawing_set_keyboard (GKBD_KEYBOARD_DRAWING (typer->
          keyboard.layout), &typer->keyboard.names);
  gtk_widget_set_tooltip_text (typer->keyboard.layout, "Key pressed");
}

static void
gnome_typer_bar_init (GnomeTyper * typer)
{
  typer->bar.status = gtk_statusbar_new ();
  typer->bar.progress = gtk_progress_bar_new ();
  gtk_widget_set_tooltip_text (typer->bar.progress, "Progress of task (%)");
  gtk_box_pack_end (GTK_BOX (typer->bar.status), typer->bar.progress, FALSE,
      TRUE, 1);
  gtk_widget_set_tooltip_text (typer->bar.status, "Status of Typer");
}

static void
gnome_typer_window_on_destroy (GtkWidget * widget, gpointer data)
{
  GnomeTyper *typer = GNOME_TYPER (data);
  pango_attr_list_unref (typer->attribute);
  if (typer->timing.sourceid) {
    g_source_remove (typer->timing.sourceid);
    typer->timing.sourceid = 0;
    g_timer_stop (typer->timing.timer);
  }
  g_timer_destroy (typer->timing.timer);
}

static void
gnome_typer_activate (GApplication * application)
{
  GnomeTyper *typer = GNOME_TYPER (application);
  GkbdKeyboardDrawingGroupLevel grouplevels[4] =
      { {0, 0}, {1, 3}, {1, 3}, {1, 3} };
  GtkWidget *vbox;
  guint i;

  for (i = 0; i < 4; i++) {
    typer->keyboard.grouplevels[i] = grouplevels[i];
    typer->keyboard.pgrouplevels[i] = &typer->keyboard.grouplevels[i];
  }

  typer->window = gtk_application_window_new (GTK_APPLICATION (typer));
  gtk_window_set_position (GTK_WINDOW (typer->window),
      GTK_WIN_POS_CENTER_ALWAYS);
  gtk_window_set_application (GTK_WINDOW (typer->window),
      GTK_APPLICATION (application));
  gtk_window_set_title (GTK_WINDOW (typer->window), PACKAGE_NAME);
  gtk_window_set_default_icon_from_file (ICONSDIR "/" PACKAGE ".png", NULL);

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);

  gnome_typer_toolbar_init (typer);
  gtk_container_add (GTK_CONTAINER (vbox),
      gtk_ui_manager_get_widget (typer->controller, "/ToolBar"));

  gtk_box_pack_start (GTK_BOX (vbox), gnome_typer_info_init (typer), FALSE,
      TRUE, 1);

  gnome_typer_keyboard_init (typer);
  gtk_box_pack_start (GTK_BOX (vbox), typer->keyboard.layout, TRUE, TRUE, 1);

  gnome_typer_bar_init (typer);

  gtk_box_pack_start (GTK_BOX (vbox), typer->bar.status, FALSE, TRUE, 1);
  gtk_container_add (GTK_CONTAINER (typer->window), vbox);
  g_signal_connect (G_OBJECT (typer->window), "destroy",
      G_CALLBACK (gnome_typer_window_on_destroy), typer);
  gtk_widget_show_all (GTK_WIDGET (typer->window));

  gtk_window_set_focus (GTK_WINDOW (typer->window), typer->info.entry);
  gtk_statusbar_push (GTK_STATUSBAR (typer->bar.status), 0, "Ready");

}

#define gnome_typer_lesson_activated(lesson) \
	static void \
	gnome_typer_lesson## lesson ##_activated (GSimpleAction *action, GVariant *parameter, gpointer data) \
	{ \
		gnome_typer_lesson##lesson(data); \
	}

gnome_typer_lesson_activated (1);
gnome_typer_lesson_activated (2);
gnome_typer_lesson_activated (3);
gnome_typer_lesson_activated (4);
gnome_typer_lesson_activated (5);
gnome_typer_lesson_activated (6);
gnome_typer_lesson_activated (7);
gnome_typer_lesson_activated (8);
gnome_typer_lesson_activated (9);
gnome_typer_lesson_activated (10);
gnome_typer_lesson_activated (11);
gnome_typer_lesson_activated (12);

static void
gnome_typer_open_activated (GSimpleAction * action, GVariant * parameter,
    gpointer data)
{
  gnome_typer_open (data);
}

static void
gnome_typer_fortune_activated (GSimpleAction * action, GVariant * parameter,
    gpointer data)
{
  gnome_typer_fortune (data);
}

static void
gnome_typer_about_activated (GSimpleAction * action, GVariant * parameter,
    gpointer data)
{
  gnome_typer_about (data);
}

static void
gnome_typer_help_activated (GSimpleAction * action, GVariant * parameter,
    gpointer data)
{
  gnome_typer_help (data);
}

static void
gnome_typer_quit_activated (GSimpleAction * action, GVariant * parameter,
    gpointer data)
{
  g_application_quit (data);
}

static const gchar *gnome_typer_ui_interface =
    "<interface>"
    "  <menu id='app-menu'>"
    "    <section>"
    "      <submenu>"
    "        <attribute name='label' translatable='yes'>_Lessons</attribute>"
    "        <section>"
    "          <item>"
    "            <attribute name='label' translatable='yes'>Lesson _1</attribute>"
    "            <attribute name='action'>app.lesson1</attribute>"
    "            <attribute name='accel'>&lt;Primary&gt;1</attribute>"
    "          </item>"
    "          <item>"
    "            <attribute name='label' translatable='yes'>Lesson _2</attribute>"
    "            <attribute name='action'>app.lesson2</attribute>"
    "            <attribute name='accel'>&lt;Primary&gt;2</attribute>"
    "          </item>"
    "          <item>"
    "            <attribute name='label' translatable='yes'>Lesson _3</attribute>"
    "            <attribute name='action'>app.lesson3</attribute>"
    "            <attribute name='accel'>&lt;Primary&gt;3</attribute>"
    "          </item>"
    "          <item>"
    "            <attribute name='label' translatable='yes'>Lesson _4</attribute>"
    "            <attribute name='action'>app.lesson4</attribute>"
    "            <attribute name='accel'>&lt;Primary&gt;4</attribute>"
    "          </item>"
    "          <item>"
    "            <attribute name='label' translatable='yes'>Lesson _5</attribute>"
    "            <attribute name='action'>app.lesson5</attribute>"
    "            <attribute name='accel'>&lt;Primary&gt;5</attribute>"
    "          </item>"
    "          <item>"
    "            <attribute name='label' translatable='yes'>Lesson _6</attribute>"
    "            <attribute name='action'>app.lesson6</attribute>"
    "            <attribute name='accel'>&lt;Primary&gt;6</attribute>"
    "          </item>"
    "          <item>"
    "            <attribute name='label' translatable='yes'>Lesson _7</attribute>"
    "            <attribute name='action'>app.lesson7</attribute>"
    "            <attribute name='accel'>&lt;Primary&gt;7</attribute>"
    "          </item>"
    "          <item>"
    "            <attribute name='label' translatable='yes'>Lesson _8</attribute>"
    "            <attribute name='action'>app.lesson8</attribute>"
    "            <attribute name='accel'>&lt;Primary&gt;8</attribute>"
    "          </item>"
    "          <item>"
    "            <attribute name='label' translatable='yes'>Lesson _9</attribute>"
    "            <attribute name='action'>app.lesson9</attribute>"
    "            <attribute name='accel'>&lt;Primary&gt;9</attribute>"
    "          </item>"
    "          <item>"
    "            <attribute name='label' translatable='yes'>Lesson 1_0</attribute>"
    "            <attribute name='action'>app.lesson10</attribute>"
    "            <attribute name='accel'>&lt;Primary&gt;0</attribute>"
    "          </item>"
    "          <item>"
    "            <attribute name='label' translatable='yes'>Lesson 11</attribute>"
    "            <attribute name='action'>app.lesson11</attribute>"
    "            <attribute name='accel'>&lt;Primary&gt;a</attribute>"
    "          </item>"
    "          <item>"
    "            <attribute name='label' translatable='yes'>Lesson 12</attribute>"
    "            <attribute name='action'>app.lesson12</attribute>"
    "            <attribute name='accel'>&lt;Primary&gt;b</attribute>"
    "          </item>"
    "        </section>"
    "      </submenu>"
    "    </section>"
    "    <section>"
    "      <item>"
    "        <attribute name='label' translatable='yes'>_Open</attribute>"
    "        <attribute name='action'>app.open</attribute>"
    "        <attribute name='accel'>&lt;Primary&gt;o</attribute>"
    "      </item>"
    "      <item>"
    "        <attribute name='label' translatable='yes'>_Fortune</attribute>"
    "        <attribute name='action'>app.fortune</attribute>"
    "        <attribute name='accel'>&lt;Primary&gt;f</attribute>"
    "      </item>"
    "    </section>"
    "    <section>"
    "      <item>"
    "        <attribute name='label' translatable='yes'>_Help</attribute>"
    "        <attribute name='action'>app.help</attribute>"
    "        <attribute name='accel'>&lt;Primary&gt;h</attribute>"
    "      </item>"
    "      <item>"
    "        <attribute name='label' translatable='yes'>_About</attribute>"
    "        <attribute name='action'>app.about</attribute>"
    "        <attribute name='accel'>&lt;Primary&gt;a</attribute>"
    "      </item>"
    "      <item>"
    "        <attribute name='label' translatable='yes'>_Quit</attribute>"
    "        <attribute name='action'>app.quit</attribute>"
    "        <attribute name='accel'>&lt;Primary&gt;q</attribute>"
    "      </item>" "    </section>" "  </menu>" "</interface>";

static void
gnome_typer_startup (GApplication * application)
{
  GnomeTyper *typer = GNOME_TYPER (application);
  GtkBuilder *builder;
  const GActionEntry gnome_typer_application_entries[] = {
    {"lesson1", gnome_typer_lesson1_activated, NULL, NULL, NULL},
    {"lesson2", gnome_typer_lesson2_activated, NULL, NULL, NULL},
    {"lesson3", gnome_typer_lesson3_activated, NULL, NULL, NULL},
    {"lesson4", gnome_typer_lesson4_activated, NULL, NULL, NULL},
    {"lesson5", gnome_typer_lesson5_activated, NULL, NULL, NULL},
    {"lesson6", gnome_typer_lesson6_activated, NULL, NULL, NULL},
    {"lesson7", gnome_typer_lesson7_activated, NULL, NULL, NULL},
    {"lesson8", gnome_typer_lesson8_activated, NULL, NULL, NULL},
    {"lesson9", gnome_typer_lesson9_activated, NULL, NULL, NULL},
    {"lesson10", gnome_typer_lesson10_activated, NULL, NULL, NULL},
    {"lesson11", gnome_typer_lesson11_activated, NULL, NULL, NULL},
    {"lesson12", gnome_typer_lesson12_activated, NULL, NULL, NULL},
    {"open", gnome_typer_open_activated, NULL, NULL, NULL},
    {"fortune", gnome_typer_fortune_activated, NULL, NULL, NULL},
    {"help", gnome_typer_help_activated, NULL, NULL, NULL},
    {"about", gnome_typer_about_activated, NULL, NULL, NULL},
    {"quit", gnome_typer_quit_activated, NULL, NULL, NULL},
  };

  G_APPLICATION_CLASS (gnome_typer_parent_class)->startup (application);

  g_action_map_add_action_entries (G_ACTION_MAP (application),
      gnome_typer_application_entries,
      G_N_ELEMENTS (gnome_typer_application_entries), typer);

  builder = gtk_builder_new ();
  gtk_builder_add_from_string (builder, gnome_typer_ui_interface, -1, NULL);
  gtk_application_set_app_menu (GTK_APPLICATION (application),
      G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu")));
  g_object_unref (builder);
}

static void
gnome_typer_shutdown (GApplication * application)
{
  G_APPLICATION_CLASS (gnome_typer_parent_class)->shutdown (application);
}

static void
gnome_typer_init (GnomeTyper * application)
{
}

static void
gnome_typer_finalize (GObject * object)
{
  gnome_typer_clear_task (GNOME_TYPER (object));
  G_OBJECT_CLASS (gnome_typer_parent_class)->finalize (object);
}

static void
gnome_typer_class_init (GnomeTyperClass * class)
{
  G_APPLICATION_CLASS (class)->startup = gnome_typer_startup;
  G_APPLICATION_CLASS (class)->shutdown = gnome_typer_shutdown;
  G_APPLICATION_CLASS (class)->activate = gnome_typer_activate;

  G_OBJECT_CLASS (class)->finalize = gnome_typer_finalize;
}

GnomeTyper *
gnome_typer_new (void)
{
  g_type_init ();

  g_set_application_name (PACKAGE_NAME);

  return g_object_new (gnome_typer_get_type (), "application-id",
      "org.gnome.education.typer", "flags", G_APPLICATION_HANDLES_OPEN,
      "register-session", TRUE, NULL);
}
